package org.dtrd.modules.record.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.dtrd.common.util.CheckUtil;
import org.dtrd.common.util.DateUtils;
import org.dtrd.config.system.Constant;
import org.dtrd.config.system.Constant.RecordType;
import org.dtrd.modules.achievement.service.IDtrdEntRdSigninRecordService;
import org.dtrd.modules.archive.entity.po.DtrdEntRdArchiveBasic;
import org.dtrd.modules.archive.service.IDtrdEntRdArchiveBasicService;
import org.dtrd.modules.record.entity.bean.*;
import org.dtrd.modules.record.entity.bean.CheckinRecord.RecordBuilder;
import org.dtrd.modules.record.entity.po.DtrdEntRdSigninHeightWeight;
import org.dtrd.modules.record.mapper.DtrdEntRdHeightWeightMapper;
import org.dtrd.modules.record.service.IHeightWeightRecordService;
import org.dtrd.modules.util.MathUtil;
import org.dtrd.modules.util.SchemeUtil;
import org.dtrd.common.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author 萧
 * @date:2021-08-22
 **/
@Service
@Slf4j
@DS("multi-datasource1")
public class HeightWeightRecordServiceImpl extends ServiceImpl<DtrdEntRdHeightWeightMapper, DtrdEntRdSigninHeightWeight> implements IHeightWeightRecordService {

    @Autowired
    private IDtrdEntRdArchiveBasicService basicService;
    @Autowired
    private IDtrdEntRdSigninRecordService signinRecordService;

    @Override
    public DtrdEntRdSigninHeightWeight transformRecord(CheckinRecord record) {
        Integer patientId = record.getPatientId();
        Float weight = record.getWeight();
        DtrdEntRdSigninHeightWeight dt = new DtrdEntRdSigninHeightWeight().init();
        dt.setHeightWeightWeight(weight);
        dt.setHeightWeightRemark(record.getRemark());
        dt.setPatientId(patientId);
        dt.setRecordTime(parseStringAsDateTime(record.getRecordTime()));
        dt.setRecordDate(parseStringAsDate(record.getRecordTime()));
        dt.setRecordPhoto(record.getImages());
        // 补上身高和体重的数据
        Float height = this.getHeightRecordLatest(patientId);
        dt.setHeightWeightHeight(height);
        if (height != null) {
            dt.setHeightWeightBmi(SchemeUtil.getBmi(height, weight));
        }
        return dt;
    }

    @Override
    public CheckinRecord transformPO(DtrdEntRdSigninHeightWeight po) {
        if (po == null) {
            return null;
        }
        CheckinRecord record = new CheckinRecord();
        record.setRecordTime(DateUtils.formatTime(po.getRecordTime()));
        record.setRecordTimeHm(DateUtils.formatShortTime(po.getRecordTime()));
        record.setDataId(po.getDataId());
        record.setImages(po.getRecordPhoto());
        record.setPatientId(po.getPatientId());
        record.setRecordType(Constant.RecordType.WEIGHT.getType());
        record.setRecordTypeName(Constant.RecordType.WEIGHT.getName());
        record.setRemark(po.getHeightWeightRemark());
        record.setWeight(po.getHeightWeightWeight());
        return record;
    }

    @Override
    public LambdaQueryWrapper<DtrdEntRdSigninHeightWeight> generateSelectRecordByDateQuery(Integer patientId, String date, Integer subType) {
        LambdaQueryWrapper<DtrdEntRdSigninHeightWeight> query = new LambdaQueryWrapper<>();
        query.eq(DtrdEntRdSigninHeightWeight::getIsDel, 0)
                .eq(DtrdEntRdSigninHeightWeight::getPatientId, patientId)
                .eq(DtrdEntRdSigninHeightWeight::getRecordDate, date);
        return query;
    }

    @Override
    public Float getPatientBmi(Integer patientId, Date queryDate) {
        LambdaQueryWrapper<DtrdEntRdSigninHeightWeight> wrapper = Wrappers.lambdaQuery(DtrdEntRdSigninHeightWeight.class)
                .eq(DtrdEntRdSigninHeightWeight::getPatientId, patientId)
                .eq(DtrdEntRdSigninHeightWeight::getRecordDate, queryDate)
                .orderByDesc(DtrdEntRdSigninHeightWeight::getCreateTime)
                .last("limit 1");
        DtrdEntRdSigninHeightWeight heightWeight = getOne(wrapper);
        return Optional.ofNullable(heightWeight).map(DtrdEntRdSigninHeightWeight::getHeightWeightBmi).orElse(null);
    }

    @Override
    public PeriodBmi getBmiDataForCharts(int patientId, String startDate, String endDate) {
        List<DtrdEntRdSigninHeightWeight> list = baseMapper.getBmiDataForCharts(patientId, startDate, endDate);
        PeriodBmi record = new PeriodBmi();
        List<String> dates = new ArrayList<>(list.size());
        List<Float> values1 = new ArrayList<>(list.size());
        List<Float> height = new ArrayList<>(list.size());
        List<Float> bmi = new ArrayList<>(list.size());
        List<Float> bodyFat = new ArrayList<>(list.size());
        if (CheckUtil.isNotEmpty(list)) {
            list.forEach(po -> {
                dates.add(DateUtils.formatDate(po.getRecordDate()));
                values1.add(po.getHeightWeightWeight());
                height.add(po.getHeightWeightHeight());
                bmi.add(po.getHeightWeightBmi());
                bodyFat.add(po.getHeightWeightBodyFat());
            });
        }
        record.setDates(dates);
        ArrayList<List<Float>> valueList = new ArrayList<>(1);
        valueList.add(bmi);
        valueList.add(values1);
        record.setValue(valueList);
        ArrayList<List<Float>> bmiList = new ArrayList<>(1);
        bmiList.add(bmi);
        record.setBmi(bmiList);

        record.setHeight(height);
        record.setBodyFat(bodyFat);
        return record;
    }

    @Override
    public List<HeightBean> getAllHeightWeightByUserAndDate(int patientId, String startDate, String endDate) {
        List<DtrdEntRdSigninHeightWeight> list = baseMapper.getAllHeightWeightByUserAndDate(patientId, startDate, endDate);
        return transformPoList(list);
    }

    @Override
    public List<DtrdEntRdSigninHeightWeight> getPeriodHeightWeight(Integer patientId, String startDate, String endDate) {
        return baseMapper.getAllHeightWeightByUserAndDate(patientId, startDate, endDate);
    }

    @Override
    public WeightBmiChartBean getWeightBmiDataForChartsInApp(int patientId, String startDate, String endDate) {
        Map<String, String> map = baseMapper.getWeightBmiDataForChartsInApp(patientId, startDate, endDate);
        WeightBmiChartBean chart = new WeightBmiChartBean();
        if (map != null) {
            chart.setDates(map.get("dates"));
            chart.setValues(map.get("weights"));
            chart.setValues2(map.get("bmis"));
            chart.setHeights(map.get("heights"));
            chart.setBodyFat(map.get("bodyFat"));
        }
        return chart;
    }

    @Override
    public List<HeightBean> getAllHeightWeightByPage(int patientId, int page, int pageSize) {
        int pageStart = (page - 1) * pageSize;
        List<DtrdEntRdSigninHeightWeight> list = baseMapper.getAllHeightWeightByPage(patientId, pageStart, pageSize);
        return transformPoList(list);
    }

    @Override
    public List<HeightBean> getAllHeightWeightBetweenTimeAndPage(int patientId, String startDate, String endDate, int page, int pageSize) {
        int pageStart = (page - 1) * pageSize;
        List<DtrdEntRdSigninHeightWeight> list = baseMapper.getAllHeightWeightBetweenTimeAndPage(patientId, startDate, endDate, pageStart, pageSize);
        return transformPoList(list);
    }

    @Override
    public Integer getHeightWeightRecordCount(int patientId, String startDate, String endDate) {
        return baseMapper.getHeightWeightRecordCount(patientId, startDate, endDate);
    }

    @Override
    public DtrdEntRdSigninHeightWeight getHeightWeightRecordLatest(Integer patientId) {
        LambdaQueryWrapper<DtrdEntRdSigninHeightWeight> wrapper = Wrappers.lambdaQuery(DtrdEntRdSigninHeightWeight.class)
                .eq(DtrdEntRdSigninHeightWeight::getPatientId, patientId)
                .orderByDesc(DtrdEntRdSigninHeightWeight::getRecordTime)
                .last("limit 1");
        return getOne(wrapper);
    }

    /**
     * 获取患者身高体重-BMI统计数据
     *
     * @param patientId
     * @param type
     * @param startDate
     * @param endDate
     * @param period
     * @return
     */
    @Override
    public Map getHeightWeightStatistics(int patientId, int type, String startDate, String endDate, int period) {
        Map map = new HashMap(16);
        try {
            String[] timePeriod = handleTimePeriod(startDate, endDate, patientId, type);
            if (timePeriod != null) {
                startDate = timePeriod[0];
                endDate = timePeriod[1];
            }
            // 先检测时间段内是否有数据 ，避免返回全0的记录
            Integer count = getHeightWeightRecordCount(patientId, startDate, endDate);
            if (StringUtil.StrTrimInt(count) == 0) {
                // 无记录 不作处理
                log.info("getHeightWeightStatistics." + patientId + " no record between " + startDate + " and " + endDate);
            } else {
                StatisticsBean statisticsBean = new StatisticsBean();
                Float height = this.getHeightRecordLatest(patientId);
                if (patientId > 0) {
                    statisticsBean = getHeightWeightStatisticsBean(patientId, startDate, endDate);
                    statisticsBean.setCount(statisticsBean.getLowCounts() + statisticsBean.getHighCounts() + statisticsBean.getNormalCounts());
                    statisticsBean.setLowPercent(MathUtil.calculatePercentage(statisticsBean.getLowCounts(), statisticsBean.getCount()));
                    statisticsBean.setHighPercent(MathUtil.calculatePercentage(statisticsBean.getHighCounts(), statisticsBean.getCount()));
                    statisticsBean.setNormalPercent(100 - statisticsBean.getLowPercent() - statisticsBean.getHighPercent());
                    if (height != null) {
                        statisticsBean.setBmiHighestValue(SchemeUtil.getBmi(height, statisticsBean.getHighestValue()));
                        statisticsBean.setBmiLowestValue(SchemeUtil.getBmi(height, statisticsBean.getLowestValue()));
                        statisticsBean.setBmiAvgValue(SchemeUtil.getBmi(height, statisticsBean.getAvgValue()));
                    }
                    statisticsBean.setPatientId(patientId);
                }
                map.put("statistics", statisticsBean);
            }
        } catch (Exception e) {
            log.error("BMI统计异常", e);
        }
        return map;
    }

    @Override
    public Float getHeightRecordLatest(Integer patientId) {
        // heightWeight 表里
        LambdaQueryWrapper<DtrdEntRdSigninHeightWeight> heightWeightQueryWrapper = Wrappers.lambdaQuery(DtrdEntRdSigninHeightWeight.class)
                .eq(DtrdEntRdSigninHeightWeight::getPatientId, patientId)
                .eq(DtrdEntRdSigninHeightWeight::getIsDel, 0)
                .isNotNull(DtrdEntRdSigninHeightWeight::getHeightWeightHeight)
                .orderByDesc(DtrdEntRdSigninHeightWeight::getCreateTime)
                .last("limit 1");
        DtrdEntRdSigninHeightWeight queryHeight = this.getOne(heightWeightQueryWrapper);
        // 有记录就返回对应记录的
        if (queryHeight != null) {
            return queryHeight.getHeightWeightHeight();
        }
        // 没记录查询患者档案里的
        LambdaQueryWrapper<DtrdEntRdArchiveBasic> archiveBasicQueryWrapper = Wrappers.lambdaQuery(DtrdEntRdArchiveBasic.class)
                .eq(DtrdEntRdArchiveBasic::getPatientId, patientId)
                .eq(DtrdEntRdArchiveBasic::getIsDel, 0);
        DtrdEntRdArchiveBasic basic = basicService.getOne(archiveBasicQueryWrapper);
        return Optional.ofNullable(basic).map(DtrdEntRdArchiveBasic::getHeight).orElse(null);
    }

    @Override
    public Float getFirstHeightWeightRecord(Integer patientId, Constant.HEIGHT_WEIGHT heightWeight) {
        Float value = baseMapper.getFirstHeightWeightRecord(patientId, heightWeight);
        if(value != null){
            return value;
        }
        LambdaQueryWrapper<DtrdEntRdArchiveBasic> archiveBasicQueryWrapper = Wrappers.lambdaQuery(DtrdEntRdArchiveBasic.class)
                .eq(DtrdEntRdArchiveBasic::getPatientId, patientId)
                .eq(DtrdEntRdArchiveBasic::getIsDel, 0);
        DtrdEntRdArchiveBasic basic = basicService.getOne(archiveBasicQueryWrapper);
        switch (heightWeight){
            case BMI:
                Float height = Optional.ofNullable(basic).map(DtrdEntRdArchiveBasic::getHeight).orElse(null);
                Float weight = Optional.ofNullable(basic).map(DtrdEntRdArchiveBasic::getWeight).orElse(null);
                return height != null && weight != null? (weight/height*height)*10000: null;
            case WEIGHT:
                return Optional.ofNullable(basic).map(DtrdEntRdArchiveBasic::getHeight).orElse(null);
            default:return null;
        }// 没记录查询患者档案里的
    }

    @Override
    public Float getAvgWeight(Integer patientId, String startOfWeek, String endOfWeek) {
        return baseMapper.getAvgWeight(patientId, startOfWeek, endOfWeek);
    }

    @Override
    public Integer getAvgBmi(Integer patientId, String startOfWeek, String endOfWeek) {
        return baseMapper.getAvgBmi(patientId, startOfWeek, endOfWeek);
    }

    @Override
    public boolean saveWeight(HeightBean heightWeightInfo) {
        Integer patientId = heightWeightInfo.getPatientId();
        DtrdEntRdSigninHeightWeight po = new DtrdEntRdSigninHeightWeight();
        if (heightWeightInfo.getId() == null) {
            po = new DtrdEntRdSigninHeightWeight().init();
        }
        po = po.parseFromDTO(heightWeightInfo);
        // 根据日期修改
        Date recordDate = po.getRecordDate();
        LambdaUpdateWrapper<DtrdEntRdSigninHeightWeight> wrapper = Wrappers.lambdaUpdate(DtrdEntRdSigninHeightWeight.class)
                .eq(DtrdEntRdSigninHeightWeight::getPatientId, patientId)
                .eq(DtrdEntRdSigninHeightWeight::getRecordDate, recordDate);
        boolean result = saveOrUpdate(po, wrapper);
        log.info("HeightWeightRecordServiceImpl.saveWeight.[heightWeightInfo] result=" + result + "; after save dataId=" + po.getDataId());
        if (!result) {
            return false;
        }
        if (po.getDataId() == null) {
            po = getOne(wrapper);
        }
        // 补充打卡记录
        signinRecordService.saveSigninRecord(po.getDataId(),
                0, mapper -> {
                    RecordBuilder recordBuilder = mapper.apply(patientId)
                            .withRecordType(RecordType.WEIGHT.getType())
                            .withRecordTime(heightWeightInfo.getTime())
                            .withImages(heightWeightInfo.getRecordPhoto())
                            .withRemark(heightWeightInfo.getRemark());
                    if (heightWeightInfo.getWeight() != null) {
                        recordBuilder.withRecordValue(String.valueOf(heightWeightInfo.getWeight()));
                        if (heightWeightInfo.getHeight() != null) {
                            recordBuilder.withRecordValue2(String.valueOf(heightWeightInfo.getHeight()));
                            recordBuilder.withRecordValue3(SchemeUtil.getBmi(heightWeightInfo.getHeight(), heightWeightInfo.getWeight()) + "");
                        }
                    }
                    return recordBuilder.build();
                });
        return true;
    }

    /**
     * BMI统计
     *
     * @param patientId
     * @param startDate
     * @param endDate
     * @return
     */
    private StatisticsBean getHeightWeightStatisticsBean(int patientId, String startDate, String endDate) {
        if (CheckUtil.isEmpty(startDate)) {
            startDate = "1970-01-01";
        }
        if (CheckUtil.isEmpty(endDate)) {
            endDate = DateUtils.formatDate();
        }
        float bmiLow = 18.5f;
        float bmiHigh = 24f;
        return baseMapper.getHeightWeightStatisticsBean(patientId, startDate, endDate, bmiHigh, bmiLow);
    }

    /**
     * 处理请求时间
     *
     * @param
     * @param type 0、2直接获取参数值，3 以当天为结束日期按照指定周期倒推开始时间
     * @return
     */
    private String[] handleTimePeriod(String startDate, String endDate, int period, int type) {
        String[] timePeriod = null;
        if (type == 0 || type == 2) {
            if (endDate.equals("")) {
                endDate = startDate;
            }
            timePeriod = new String[2];
            timePeriod[0] = startDate;
            timePeriod[1] = endDate;
        } else if (type == 3) {
            // 按照周期查询 0：全部 1：一周、2：两周、3：一月、4：两月 5 三月 6 半年 7 一年
            endDate = DateUtils.formatDate();
            int dateSub = 0;
            switch (period) {
                case 0:
                    dateSub = Integer.MIN_VALUE;
                    break;
                case 1:
                    dateSub = -6;
                    break;
                case 2:
                    dateSub = -13;
                    break;
                case 3:
                    dateSub = -29;
                    break;
                case 4:
                    dateSub = -59;
                    break;
                case 5:
                    dateSub = -89;
                    break;
                case 6:
                    dateSub = -179;
                    break;
                case 7:
                    dateSub = -364;
                    break;
                default:
                    dateSub = Integer.MIN_VALUE;
                    break;
            }
            if (dateSub == Integer.MIN_VALUE) {
                startDate = "1970-01-01";
            } else {
                startDate = DateUtils.dateAdd(dateSub);
            }
            timePeriod = new String[2];
            timePeriod[0] = startDate;
            timePeriod[1] = endDate;
        }
        return timePeriod;
    }

    private List<HeightBean> transformPoList(List<DtrdEntRdSigninHeightWeight> list) {
        if (list == null) {
            return null;
        }
        List<HeightBean> dataList = new ArrayList<>(list.size());
        list.stream().map(po -> new HeightBean().parseFromPo(po)).forEach(dataList::add);
        return dataList;
    }
}
